#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/dir.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <netdb.h>

#include "fd_list.h"
#include "file_db.h"
#include "bstrlib.h"
#include "server.h"
#include "common.h"
#include "to_index.h"
#include "internal.h"

int finish_var = 0, global_port=19500, server_num=0;
char** servers_list=NULL;

void finish(int i){
    finish_var = 1;
}

int verific(int *x)
{
  int z=0;
  z=scanf(" %i",x);
  while (z!=1)
    {
      while(scanf("%*[^\n]")!=0);
      printf ("\a\a\a\aHa ingresado mal el dato:\n");
      printf("\a\a\a\aIngrese correctamente lo requerido\n\t= ");
      z=scanf(" %i",x);
    }  
  
  return z;
}


void conf_menu(void){
    int finish=0, opt=0, i=0, del=-1;
    char *host_addr=NULL, *host_port=NULL;

    while(!finish){
        printf("****************************************************\n");
        printf("***************MENU DE CONFIGURACION****************\n");
        printf("****************************************************\n");
        printf("1. Puerto donde escuchar (actual: %i)\n", global_port);
        printf("2. Configurar el directorio de descargas\n");
        printf("3. Añadir un indice a donde conectarse\n");
        printf("4. Listar indices cargados\n");
        printf("5. Eliminar indices cargados\n");
        printf("6. Ejecutar\n");
        printf("7. Salir\n");
        printf("--> ");
        scanf("%i",&opt);
        while(opt<1 || opt>7){
            printf("\nIngrese la opcion correcta\n");
            printf("--> ");
            verific(&opt);
        }
        switch (opt){
            case 1:
            printf("\nIngrese el puerto (mayor que 1024 y menor que 65535):");
            scanf("%i",&global_port);
            while(global_port<1024 || global_port>65535){
                printf("\nIngrese la opcion correcta\n");
                printf("--> ");
                verific(&global_port);
            }
            break;

            case 3:
            printf("\nIngrese host a donde conectarse (host o ip):");
            host_addr=calloc(255, sizeof(char));
            scanf("%s", host_addr);
            printf("\nIngrese el puerto del host:");
            host_port=calloc(6, sizeof(char));
            scanf("%s", host_port);
            servers_list = realloc(servers_list, sizeof(char*)*(server_num+1));
            servers_list[server_num] = calloc((strlen(host_addr)+strlen\
                                       (host_port)+2), sizeof(char));
            strncpy(servers_list[server_num],host_addr,strlen(host_addr));
            strncat(servers_list[server_num],":",1);
            strncat(servers_list[server_num],host_port,strlen(host_port));
            server_num++;
            free(host_addr);
            host_addr = NULL;
            free(host_port);
            host_port=NULL;
            break;

            case 4:
            system("clear");
            printf("\nIndices cargados:\n");
            for(i=0;i<server_num;i++){
                printf("%s\n",servers_list[i]);
            }
            printf("\n");
            break;

            case 5:
            while(del!=0){

                system("clear");
                printf("\nIndices cargados:\n");
                for(i=0;i<server_num;i++){
                    printf("%i) %s\n",(i+1),servers_list[i]);
                }
                printf("\n");

                printf("Ingrese numero de indice a eliminar (0 para salir): ");
                scanf("%i",&del);
                while(del<0 || del>server_num){
                    printf("\nIngrese la opcion correcta\n");
                    printf("--> ");
                    verific(&del);
                }
                if(del!=0){
                    free(servers_list[del-1]);
                    for(i=(del-1);i<server_num;i++){
                        servers_list[i] = servers_list[i+1];
                    }
                    servers_list[i]=NULL;
                    server_num--;

                }

            }
            del = -1;
            break;

            case 6:
            finish=1;
            break;

            case 7:
            for(i=0;i<server_num;i++){
                free(servers_list[i]);
                servers_list[i]=NULL;
            }
            free(servers_list);
            servers_list=NULL;
            system("clear");
            printf("Adiós\n");
            exit(1);
        }
    }


}


int setup_connection(void){
    /*Funcion utilizada para crear el socket y dejarlo escuchando en
    el puerto*/
    int fd_server = 0, yes=1;
    struct sockaddr_in server;

    /*Creamos el socket con protocolo de internet IPv4, de tipo stream
    (confiable)*/
    fd_server = socket(PF_INET, SOCK_STREAM, 0);
    if (fd_server == -1){
        perror("socket()");
        exit(1);
    }

    /*seteamos los valores de puerto y direccion del server utilizando
    funciones para convertir los ordenes de bytes, aumentando la portabilidad
    a otros sistemas*/

    server.sin_family = AF_INET;
    server.sin_port = htons(global_port);
    /*INADDR_ANY se usa para autodetectar la direccion del servidor*/
    server.sin_addr.s_addr = htonl(INADDR_ANY); 

    /*Seteamos el socket para permitir que el puerto sea reusado*/
    if (setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR,\
        &yes, sizeof(int)) == -1){
        perror("setsockopt");
        exit(1);
    }

    /*Bindeamos el socket con el puerto y direccion en el servidor*/
    if(bind(fd_server, (struct sockaddr*)&server,\
       sizeof(struct sockaddr)) == -1){
        perror("bind()");
        exit(1);
    }

    /*Empezamos a escuchar conexiones entrantes, vamos a atender un maximo de
    Q_CONEX conexiones*/
    if(listen(fd_server, Q_CONEX) == -1) {
        perror("listen()");
        exit(1);
    }

    printf("corriendo...\n");

    return fd_server;
}

int listen_and_accept_connection(int fd_server){
    int aux_size = 0, fd_client = 0;
    char *aux_inet = NULL;
    struct sockaddr_in client;


    assert(fd_server != 0);
    aux_size = sizeof(struct sockaddr);
    /*Aceptamos la conexion entrante*/
    fd_client = accept(fd_server, (struct sockaddr *)&client,\
                (socklen_t *)&aux_size);
    if (fd_client < 0) {
        perror("accept()");
        finish(1);
    }
    aux_inet = inet_ntoa(client.sin_addr);
    printf("Se obtuvo una conexion desde %s\n", aux_inet);
    return fd_client;

}


int main(int argc, char* argv[]){
    int fd_server=0, fd_client=0, i=0, j=0, h=0, s=0, maxDescriptor=0,\
        result=0, connection=0, sep=0, command=0;
    char *host_addr=NULL, *host_port=NULL, *ip_port=NULL, *std_in=NULL;
    fd_set read_fds, write_fds;
    fd_list list = NULL;
    struct sockaddr_in dest_addr;
    struct hostent *host;
    struct timeval tv;

    /*Creamos nuestra lista de archivos*/
    files_db files;

    files = files_create();

    /*Cargamos los archivos del file system*/
    load_files(files);


    signal(SIGPIPE,SIG_IGN);

    signal(SIGHUP, finish);
    signal(SIGINT, finish);
    signal(SIGTERM, finish);


    system("clear");
    printf("                 .-\"\"-.\n");
    printf("                /      \\\n");
    printf("               /     (0 \\______\n");
    printf("               |         \"_____)\n");
    printf("               \\        ,-----' \n");
    printf("                \\_    _/\n");
    printf("                 /    \\\n");
    printf("                /      \\\n");
    printf("               /        \\\n");
    printf("              /          |\n");
    printf("             /        :  |\n");
    printf("            /     ;   :  |\n");
    printf("   \\\\\\     /  _.-'    :  |\n");
    printf("    \\\\\\\\  / _'        :  |\n");
    printf("     \\\\\\\\/ ;         :   /\n");
    printf("      \\\\  ;         :   /\n");
    printf("       \\   `._`-'_.'  _/\n");
    printf("        \\     ''' _.-' \n");
    printf("         \\      / /\n");
    printf("          \\    / /  \n");
    printf("           \\  /)(_______\n");
    printf("            )(_________< \n");
    printf("           (__________< \n");
    printf("\n\n*******************************\n");
    printf("***** Bienvenido a Server *****\n");
    printf("**********  V 0.9 b  **********\n");
    printf("*******************************\n");
    servers_list = calloc(1, sizeof(char*));

    if (argc==1){
        printf("\nNo se utilizaron parametros, cargando menu...\n\n");
        conf_menu();
    }

    else{
        global_port = atol(argv[1]);
        for (s=2;s<argc;s++){
    		/*Cargo en servers_list la lista de trackers*/
            servers_list = realloc(servers_list, sizeof(char*)*(server_num+1));
            servers_list[server_num] = calloc(261, sizeof(char));
            strncpy(servers_list[server_num], argv[s], strlen(argv[s]));
            server_num++;
        }
    }

    /*Creamos nuestra lista de file descriptors*/
    list = fd_list_create();

    for(s=0;s<server_num;s++){
    	/*Por cada item en la lista de trackers, abro una conexion y dejo 
    	  seteado que le tengo que mandar el hello port
    	*/
        host_addr=calloc(255, sizeof(char));
        host_port=calloc(6, sizeof(char));

        sep=0;
        h=0;
        for(j=0;(unsigned int)j<strlen(servers_list[s]);j++){
            if(servers_list[s][j] == ':'){
                sep=1;
            }
            else{
                if(!sep){
                    host_addr[j] = servers_list[s][j];
                }
                else{
                    host_port[h] = servers_list[s][j];
                    h++;
                }
            }
        }
        result = socket(PF_INET, SOCK_STREAM, 0);
        if (result == -1) {
            printf("Error al crear el Socket");
            return -1;
        }
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(atol(host_port));
        host = gethostbyname(host_addr);
        dest_addr.sin_addr= (*( struct in_addr*)(host -> h_addr_list[0]));
    
        connection = connect(result, (struct sockaddr *)&dest_addr,\
                    sizeof(struct sockaddr));
        if (connection != 0) {
            printf("Error al intentar establecer la conexión\n");
            perror("connect");
            return -1;
        }
        if (result>maxDescriptor){
            maxDescriptor = result;
        }
        /*Cargo el item recien creado en la estructura*/
        fd_list_add_fd(list, result);
        i = fd_list_search(list, result);
        fd_list_index(list, i);
        fd_list_set_cmd(list, i, 0);
        fd_list_not_finished_attending(list, i);
        fd_list_sending(list, i);
        fd_list_finished_receiving(list, i);
        fd_list_not_peer(list, i);
        
        ip_port = calloc(((strlen(host_addr))+7),sizeof(char));
        if(ip_port == NULL){
        	perror("ip_port_main");
        	return -1;
        }
        strncat(ip_port, host_addr, strlen(host_addr));
        strncat(ip_port, ":", 1);
        strncat(ip_port, host_port, strlen(host_port));
        
        fd_list_set_ip_port(list, i, ip_port);
        
        free(ip_port);
        ip_port=NULL;
        free(host_addr);
        host_addr=NULL;
        free(host_port);
        host_port=NULL;
    }

    for(i=0;i<server_num;i++){
        free(servers_list[i]);
        servers_list[i]=NULL;
    }
    free(servers_list);
    servers_list=NULL;

    /*Seteamos lo necesario para quedarnos escuchando en el puerto*/
    fd_server = setup_connection();

    /*cargamos el valor del fd_server como el descriptor mas grande (en caso que lo sea)*/
    if (fd_server>maxDescriptor){
        maxDescriptor = fd_server;
    }

    /*Bucle que mantenemos infinitamente para aceptar todas las conexiones
    entrantes*/

    while(!finish_var){
        /*eliminamos lo que hay en cada estructura*/
        FD_ZERO(&read_fds);
        FD_ZERO(&write_fds);
        FD_SET(fd_server, &read_fds);
        tv.tv_sec = 0;
        tv.tv_usec = 1000;
        /*Aqui nos fijamos que fd's nos enviaron pedido de quit o
        cerraron su conexion repentinamente, removiendolos de nuestra
        lista*/
        for (j = 0; j < (fd_list_size(list)); j++){
            if(fd_list_get_cmd(list, j) == 6){
                /*Si el comando fue el 6, entonces el cliente nos ha
                pedido salir, a lo que respondemos, cerramos y removemos
                de la lista*/
                #ifdef __DEBUG__
                    printf("El fd %i nos mandó un quit\n", fd_list_get_fd(list, j));
                #endif
                send_msg(fd_list_get_fd(list, j), MSG0);
                close(fd_list_get_fd(list, j));
                fd_list_remove(list, j);
            }
            if(fd_list_is_peer(list, j) && fd_list_get_cant(list, j)==0){
                /*Es un peer del que no tengo más nada que recibir*/
                #ifdef __DEBUG__
                    printf("No tengo más nada que recibir de %i, lo saco\n",\
                    fd_list_get_fd(list, j));
                #endif
                send_msg(fd_list_get_fd(list, j), "quit\r\n");
                close(fd_list_get_fd(list, j));
                fd_list_remove(list, j);
            }
        }
        /*dentro de este for metemos en la estructura read_fds los fd
        de los que esperamos el cliente nos envie cosas, y en la
        estructura write_fds metemos a los que nos faltan datos para enviar*/
        for (j = 0; j < (fd_list_size(list)); j++){
            if(!fd_list_am_i_sending(list, j)){
                /*si no estoy enviando entonces vamos a quedarnos a escuchar*/
                FD_SET(fd_list_get_fd(list, j), &read_fds);
            }
            else{
                /*si no, lo ponemos en la
                estructura de "para escribir"*/
                FD_SET(fd_list_get_fd(list, j), &write_fds);
            }
        }
        /*Metemos el stdin como fd de donde debe leer*/
        FD_SET(0, &read_fds);
        if (select(maxDescriptor + 1, &read_fds, &write_fds, NULL, &tv) == -1){
            perror("select()");
            printf("Cerrando Servidor...\n");
            close(fd_server);
            fd_list_destroy(list);
            list=NULL;
            exit(1);
        }
        /*Recorremos todos los file descriptors en busca de quienes
        esperan algo, o quieren enviar algo*/
        for (i = 0; i <= maxDescriptor; i++){
            /*si entramos en este if es porque llego algo de un cliente*/
            if (FD_ISSET(i, &read_fds)){
                if (i == 0){
                    /*Recibi del stdin, lo analizo*/
                    #ifdef __DEBUG__
                        printf("Recibi del stdin, lo analizo\n");
                    #endif
                    std_in = cargar();
                    load_get_file_listing(list, std_in);
                }
                /*Recibimos un intento de conexion*/
                else if (i == fd_server){
                    /*tomamos la nueva conexion y la cargamos en nuestra
                    lista*/
                    #ifdef __DEBUG__
                        printf("Recibi un intento de conexion\n");
                    #endif
                    fd_client = listen_and_accept_connection(fd_server);
                    if (fd_client > maxDescriptor){
                        maxDescriptor = fd_client;
                    }
                    fd_list_add_fd(list, fd_client);
                }
                /*Si no entro al if anterior, quiere decir que quizas
                algun cliente nos esta pidiendo algo, con lo cual
                usamos la siguiente funcion para ver si el fd se encuentra
                en nuestra lista, caso contrario obtendremos un -1*/
                else{
                    j = fd_list_search(list, i);
                    /*Si el fd se encuentra en la lista, entonces un cliente
                    nos ha hecho un pedido o nos está respondiendo algo*/
                    if (j == -1){
                        /*Si el fd no esta en la lista, damos otra 
                          vuelta al bucle*/
                        continue;
                    }
                    /*El fd se encuentra en nuestra lista, ahora analizamos
                      si es una respuesta a algo que enviamos o si nos estan
                      pidiendo algo
                      */
                    if(fd_list_has_finished_attending(list, j)){
                        /*Si he terminado de atenderlo, quiere decir que
                            estoy recibiendo un comando, lo parseo*/
                        #ifdef __DEBUG__
                            printf("El fd %i me manda un comando\n",\
                                fd_list_get_fd(list, j));
                        #endif
                        /*receive_info(list, j);*/
                    }
                    else{
                        /*Si entro aca, es porque no he terminado de atenderlo
                            por ende, me estan o respondiendo a algo que pedi
                            o todavia me estan enviando cosas*/
                        get_response(list, j);
                        if(fd_list_get_answ(list, j)==-2){
                            /*Tengo que escuchar una respuesta
                                a algo que mandé
                                */
                            #ifdef __DEBUG__
                                printf("el fd %i me esta respondiendo\n",\
                                fd_list_get_fd(list, j));
                            #endif
                            analize_response(list, j);
                        }
                    }
                }
            }

        }

        for (i=0; i < fd_list_size(list); i++){
            /*No me mandaron nada, pero quizás tengo que seguir analizando
            lo que ya me han respondido*/
            if(!fd_list_has_finished_attending(list,i)&&
               !fd_list_am_i_sending(list, i)){
                if(fd_list_get_answ(list, i)==0){
                    /*Si entro a este if, es porque ya me habian
                        respondido correctamente, debo analizar
                        en que comando estaba para continuar*/
                    #ifdef __DEBUG__
                    printf("sigo analizando el fd %i\n",\
                        fd_list_get_fd(list, i));
                    #endif
                    command = fd_list_get_cmd(list, i);
                    if(command==0){
                        /*Si entro aca es porque me respondieron bien al hello*/
                        /*Seteo la estructura para mandar los new_fragments*/
                        #ifdef __DEBUG__
                            printf("Termine con el hello del fd %i\n",\
                            fd_list_get_fd(list, i));
                        #endif
                        fd_list_set_cmd(list, i, 4);
                        fd_list_sending(list, i);
                        fd_list_finished_attending(list, i);
                        fd_list_clear_data(list, i);
                    }

                }
                else if(fd_list_get_answ(list, i)!=-2 &&
                        fd_list_get_answ(list, i)!=-1){
                    /*El servidor me respondio con error
                        analizo cual fue, y decido si tengo
                        que desconectar el nodo o no*/
                    #ifdef __DEBUG__
                        printf("el fd %i me tiro error\n",\
                        fd_list_get_fd(list, i));
                    #endif
                }
            }
        }

        /*Una vez que sali del analisis de fd's, voy a buscar los nodos
        index, para realizar los pedidos*/
        for (i=0; i < fd_list_size(list); i++){
            if (fd_list_is_index(list, i)&&
                fd_list_am_i_sending(list, i)){
                command = fd_list_get_cmd(list, i);
                if (command == 0){
                    /*Tengo que mandar el hello*/
                    if(send_hello(list, i, global_port)==1){
                        /*Mande bien el hello, seteo los valores necesarios
                        en la estructura*/
                        fd_list_receiving(list, i);
                        fd_list_set_answ(list, i, -2);
                        fd_list_not_finished_attending(list, i);
                    }
                    else{
                        /*Hubo un error al enviar el hello*/
                    }
                }
                else if (command == 4){
                    /*Tengo que enviar todos los new_fragment*/
                    fd_list_not_finished_attending(list, i);
                }
            }
        }
    finish_var = 1;
    }
    printf("Cerrando Servidor...\n");
    close(fd_server);
    fd_list_destroy(list);
    list=NULL;
    files_destroy(files);
    files=NULL;
    exit(1);
}

